package weka;

import weka.classifiers.Classifier;
import weka.classifiers.CostMatrix;
import weka.classifiers.Evaluation;
import weka.core.*;
import weka.core.converters.ConverterUtils;
import weka.gui.explorer.Messages;
import weka.gui.visualize.Plot2D;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

/**
 * Created by taohj on 2014/12/29.
 */
public class ClassifierHelpper extends AbstractHelpper {


    public ClassifierHelpper(String location, String classifierClass, String option) throws Exception {
        super(location, classifierClass, option);
    }

    public static void main(String[] args) throws Exception {
        System.out.println(new ClassifierHelpper("C:\\Users\\taohj\\Desktop\\data\\Drug.csv", "weka.classifiers.trees.J48", "").start());
    }


    @Override
    public StringBuffer start() throws Exception {

        CostMatrix costMatrix = null;
        Instances inst = new Instances(this.getM_Instances());
        ConverterUtils.DataSource source = null;
        Instances userTestStructure = null;
        // additional vis info (either shape type or point size)
        FastVector plotShape = new FastVector();
        FastVector plotSize = new FastVector();
        Instances predInstances = null;

        // for timing
        long trainTimeStart = 0, trainTimeElapsed = 0;


        boolean outputModel = true;
        boolean outputConfusion =true;
        boolean outputPerClass = true;
        boolean outputSummary = true;
        boolean outputEntropy = false;
        boolean saveVis = true;
        boolean outputPredictionsText = false;

        String grph = null;

        int testMode = 0;
        int numFolds = 10;
        double percent = 66;

        int classIndex=this.getM_Instances().numAttributes()-1;

        Classifier classifier = (Classifier)Class.forName(this.getClassName()).newInstance();
        Classifier template = null;
        template = Classifier.makeCopy(classifier);

        Classifier fullClassifier = null;
        StringBuffer outBuff = new StringBuffer();
        String name = (new SimpleDateFormat("HH:mm:ss - "))
                .format(new Date());
        String cname = classifier.getClass().getName();
        if (cname.startsWith("weka.classifiers.")) {
            name += cname.substring("weka.classifiers.".length());
        } else {
            name += cname;
        }
        String cmd = classifier.getClass().getName();

        if (classifier instanceof OptionHandler) {
            classifier.setOptions(Utils.splitOptions(this.getOption()));
            cmd += " "
                    + Utils.joinOptions((classifier).getOptions());
        }
        Evaluation eval = null;
       /* if (m_CVBut.isSelected()) {*/
        if (true) {
            testMode = 1;
            numFolds = 10;
            if (numFolds <= 1) {
                throw new Exception(Messages.getInstance().getString(
                        "ClassifierPanel_StartClassifier_Exception_Text_First"));
            }
        }

        inst.setClassIndex(classIndex);

        if (saveVis) {
            predInstances = setUpVisualizableInstances(inst);
            predInstances.setClassIndex(inst.classIndex() + 1);
        }

        outBuff.append(Messages.getInstance().getString(
                "ClassifierPanel_StartClassifier_OutBuffer_Text_First"));
        outBuff.append(Messages.getInstance().getString(
                "ClassifierPanel_StartClassifier_OutBuffer_Text_Second")
                + cname);
        if (classifier instanceof OptionHandler) {
            String[] o = ((OptionHandler) classifier).getOptions();
            outBuff.append(" " + Utils.joinOptions(o));
        }


        outBuff.append("\n");
        outBuff.append(Messages.getInstance().getString(
                "ClassifierPanel_StartClassifier_OutBuffer_Text_Fourth")
                + inst.relationName() + '\n');
        outBuff.append(Messages.getInstance().getString(
                "ClassifierPanel_StartClassifier_OutBuffer_Text_Sixth")
                + inst.numInstances() + '\n');
        outBuff.append(Messages.getInstance().getString(
                "ClassifierPanel_StartClassifier_OutBuffer_Text_Eigth")
                + inst.numAttributes() + '\n');
        if (inst.numAttributes() < 100) {
            for (int i = 0; i < inst.numAttributes(); i++) {
                outBuff.append("              " + inst.attribute(i).name()
                        + '\n');
            }
        } else {
            outBuff.append(Messages.getInstance().getString(
                    "ClassifierPanel_StartClassifier_OutBuffer_Text_Twelveth"));
        }
        outBuff.append(Messages.getInstance().getString(
                "ClassifierPanel_StartClassifier_OutBuffer_Text_Thirteenth"));

        switch (testMode) {
            case 1: // CV mode
                outBuff.append(""
                        + numFolds
                        + Messages.getInstance().getString(
                        "ClassifierPanel_StartClassifier_OutBuffer_Text_Sixteenth"));
                break;
        }

        if (costMatrix != null) {
            outBuff
                    .append(
                            Messages
                                    .getInstance()
                                    .getString(
                                            "ClassifierPanel_StartClassifier_OutBuffer_Text_TwentySecond"))
                    .append(costMatrix.toString()).append("\n");
        }
        outBuff.append("\n");

        if (outputModel || (testMode == 3) || (testMode == 4)) {


            trainTimeStart = System.currentTimeMillis();
            classifier.buildClassifier(inst);
            trainTimeElapsed = System.currentTimeMillis() - trainTimeStart;
        }

        if (outputModel) {
            outBuff.append(Messages.getInstance().getString(
                    "ClassifierPanel_StartClassifier_OutBuffer_Text_TwentySixth"));
            outBuff.append(classifier.toString() + "\n");
            outBuff
                    .append(Messages
                            .getInstance()
                            .getString(
                                    "ClassifierPanel_StartClassifier_OutBuffer_Text_TwentyEighth")
                            + Utils.doubleToString(trainTimeElapsed / 1000.0, 2)
                            + " "
                            + Messages
                            .getInstance()
                            .getString(
                                    "ClassifierPanel_StartClassifier_OutBuffer_Text_TwentyNineth"));
            if (classifier instanceof Drawable) {
                grph = null;
                try {
                    grph = ((Drawable) classifier).graph();
                } catch (Exception ex) {
                }
            }
            // copy full model for output
            SerializedObject so = new SerializedObject(classifier);
            fullClassifier = (Classifier) so.getObject();
        }


        switch (testMode) {
            case 1: // CV mode
                int rnd = 1;
                Random random = new Random(rnd);
                inst.randomize(random);
                if (inst.attribute(classIndex).isNominal()) {
                    inst.stratify(numFolds);
                }
                eval = new Evaluation(inst, costMatrix);

                if (outputPredictionsText) {
                    printPredictionsHeader(outBuff, inst, "test data");
                }

                for (int fold = 0; fold < numFolds; fold++) {
                    Instances train = inst.trainCV(numFolds, fold, random);
                    eval.setPriors(train);
                    Classifier current = null;

                    current = Classifier.makeCopy(template);
                    current.buildClassifier(train);
                    Instances test = inst.testCV(numFolds, fold);

                    for (int jj = 0; jj < test.numInstances(); jj++) {
                        processClassifierPrediction(test.instance(jj), current, eval,
                                predInstances, plotShape, plotSize);
                        if (outputPredictionsText) {
                            outBuff.append(predictionText(current, test.instance(jj),
                                    jj + 1));
                        }
                    }

                }

                if (outputPredictionsText) {
                    outBuff.append("\n");
                }
                if (inst.attribute(classIndex).isNominal()) {
                    outBuff
                            .append(Messages
                                    .getInstance()
                                    .getString(
                                            "ClassifierPanel_StartClassifier_OutBuffer_Text_ThirtyThird"));
                } else {
                    outBuff
                            .append(Messages
                                    .getInstance()
                                    .getString(
                                            "ClassifierPanel_StartClassifier_OutBuffer_Text_ThirtyFourth"));
                }
                break;
        }

        if (outputSummary) {
            outBuff.append(eval.toSummaryString(outputEntropy) + "\n");
        }

        if (inst.attribute(classIndex).isNominal()) {

            if (outputPerClass) {
                outBuff.append(eval.toClassDetailsString() + "\n");
            }

            if (outputConfusion) {
                outBuff.append(eval.toMatrixString() + "\n");
            }
        }

        return outBuff;
    }



    /**
     * Sets up the structure for the visualizable instances. This dataset contains
     * the original attributes plus the classifier's predictions for the class as
     * an attribute called "predicted+WhateverTheClassIsCalled".
     *
     * @param trainInstances the instances that the classifier is trained on
     * @return a new set of instances containing one more attribute (predicted
     *         class) than the trainInstances
     */
    public static Instances setUpVisualizableInstances(Instances trainInstances) {
        FastVector hv = new FastVector();
        Attribute predictedClass;

        Attribute classAt = trainInstances.attribute(trainInstances.classIndex());
        if (classAt.isNominal()) {
            FastVector attVals = new FastVector();
            for (int i = 0; i < classAt.numValues(); i++) {
                attVals.addElement(classAt.value(i));
            }
            predictedClass = new Attribute(
                    Messages
                            .getInstance()
                            .getString(
                                    "ClassifierPanel_SetUpVisualizableInstances_PredictedClass_Attribute_Text_First")
                            + classAt.name(), attVals);
        } else {
            predictedClass = new Attribute(
                    Messages
                            .getInstance()
                            .getString(
                                    "ClassifierPanel_SetUpVisualizableInstances_PredictedClass_Attribute_Text_Second")
                            + classAt.name());
        }

        for (int i = 0; i < trainInstances.numAttributes(); i++) {
            if (i == trainInstances.classIndex()) {
                hv.addElement(predictedClass);
            }
            hv.addElement(trainInstances.attribute(i).copy());
        }
        return new Instances(trainInstances.relationName() + "_predicted", hv,
                trainInstances.numInstances());
    }

    /**
     * outputs the header for the predictions on the data
     *
     * @param outBuff the buffer to add the output to
     * @param inst the data header
     * @param title the title to print
     */
    protected void printPredictionsHeader(StringBuffer outBuff, Instances inst,
                                          String title) {
        outBuff.append(Messages.getInstance().getString(
                "ClassifierPanel_PrintPredictionsHeader_OutBuffer_Text_First")
                + title
                + " "
                + Messages.getInstance().getString(
                "ClassifierPanel_PrintPredictionsHeader_OutBuffer_Text_First_Alpha"));
        outBuff.append(Messages.getInstance().getString(
                "ClassifierPanel_PrintPredictionsHeader_OutBuffer_Text_Second"));
        if (inst.classAttribute().isNominal()) {
            outBuff.append(Messages.getInstance().getString(
                    "ClassifierPanel_PrintPredictionsHeader_OutBuffer_Text_Third"));
        }
      /*  if (m_OutputAdditionalAttributesRange != null) {
            outBuff.append(" (");
            boolean first = true;
            for (int i = 0; i < inst.numAttributes() - 1; i++) {
                if (m_OutputAdditionalAttributesRange.isInRange(i)) {
                    if (!first) {
                        outBuff.append(",");
                    } else {
                        first = false;
                    }
                    outBuff.append(inst.attribute(i).name());
                }
            }
            outBuff.append(")");
        }*/
        outBuff.append("\n");
    }

    /**
     * Process a classifier's prediction for an instance and update a set of
     * plotting instances and additional plotting info. plotInfo for nominal class
     * datasets holds shape types (actual data points have automatic shape type
     * assignment; classifier error data points have box shape type). For numeric
     * class datasets, the actual data points are stored in plotInstances and
     * plotInfo stores the error (which is later converted to shape size values)
     *
     * @param toPredict the actual data point
     * @param classifier the classifier
     * @param eval the evaluation object to use for evaluating the classifier on
     *          the instance to predict
     * @param plotInstances a set of plottable instances
     * @param plotShape additional plotting information (shape)
     * @param plotSize additional plotting information (size)
     */
    public static void processClassifierPrediction(Instance toPredict,
                                                   Classifier classifier, Evaluation eval, Instances plotInstances,
                                                   FastVector plotShape, FastVector plotSize) {
        try {
            double pred = eval.evaluateModelOnceAndRecordPrediction(classifier,
                    toPredict);

            if (plotInstances != null) {
                double[] values = new double[plotInstances.numAttributes()];
                for (int i = 0; i < plotInstances.numAttributes(); i++) {
                    if (i < toPredict.classIndex()) {
                        values[i] = toPredict.value(i);
                    } else if (i == toPredict.classIndex()) {
                        values[i] = pred;
                        values[i + 1] = toPredict.value(i);
            /*
             * // if the class value of the instances to predict is missing then
             * // set it to the predicted value if (toPredict.isMissing(i)) {
             * values[i+1] = pred; }
             */
                        i++;
                    } else {
                        values[i] = toPredict.value(i - 1);
                    }
                }

                plotInstances.add(new Instance(1.0, values));
                if (toPredict.classAttribute().isNominal()) {
                    if (toPredict.isMissing(toPredict.classIndex())
                            || Instance.isMissingValue(pred)) {
                        plotShape.addElement(new Integer(Plot2D.MISSING_SHAPE));
                    } else if (pred != toPredict.classValue()) {
                        // set to default error point shape
                        plotShape.addElement(new Integer(Plot2D.ERROR_SHAPE));
                    } else {
                        // otherwise set to constant (automatically assigned) point shape
                        plotShape.addElement(new Integer(Plot2D.CONST_AUTOMATIC_SHAPE));
                    }
                    plotSize.addElement(new Integer(Plot2D.DEFAULT_SHAPE_SIZE));
                } else {
                    // store the error (to be converted to a point size later)
                    Double errd = null;
                    if (!toPredict.isMissing(toPredict.classIndex())
                            && !Instance.isMissingValue(pred)) {
                        errd = new Double(pred - toPredict.classValue());
                        plotShape.addElement(new Integer(Plot2D.CONST_AUTOMATIC_SHAPE));
                    } else {
                        // missing shape if actual class not present or prediction is
                        // missing
                        plotShape.addElement(new Integer(Plot2D.MISSING_SHAPE));
                    }
                    plotSize.addElement(errd);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }


    /**
     * generates a prediction row for an instance
     *
     * @param classifier the classifier to use for making the prediction
     * @param inst the instance to predict
     * @param instNum the index of the instance
     * @throws Exception if something goes wrong
     * @return the generated row
     */
    protected String predictionText(Classifier classifier, Instance inst,
                                    int instNum) throws Exception {

        // > inst# actual predicted error probability distribution

        StringBuffer text = new StringBuffer();
        // inst #
        text.append(Utils.padLeft("" + instNum, 6) + " ");
        if (inst.classAttribute().isNominal()) {

            // actual
            if (inst.classIsMissing()) {
                text.append(Utils.padLeft("?", 10) + " ");
            } else {
                text.append(Utils.padLeft("" + ((int) inst.classValue() + 1) + ":"
                        + inst.stringValue(inst.classAttribute()), 10)
                        + " ");
            }

            // predicted
            double[] probdist = null;
            double pred;
            if (inst.classAttribute().isNominal()) {
                probdist = classifier.distributionForInstance(inst);
                pred = Utils.maxIndex(probdist);
                if (probdist[(int) pred] <= 0.0) {
                    pred = Instance.missingValue();
                }
            } else {
                pred = classifier.classifyInstance(inst);
            }
            text.append(Utils.padLeft((Instance.isMissingValue(pred) ? "?"
                            : (((int) pred + 1) + ":" + inst.classAttribute().value((int) pred))),
                    10)
                    + " ");
            // error
            if (pred == inst.classValue()) {
                text.append(Utils.padLeft(" ", 6) + " ");
            } else {
                text.append(Utils.padLeft("+", 6) + " ");
            }

            // prob dist
            if (inst.classAttribute().type() == Attribute.NOMINAL) {
                for (int i = 0; i < probdist.length; i++) {
                    if (i == (int) pred) {
                        text.append(" *");
                    } else {
                        text.append("  ");
                    }
                    text.append(Utils.doubleToString(probdist[i], 5, 3));
                }
            }
        } else {

            // actual
            if (inst.classIsMissing()) {
                text.append(Utils.padLeft("?", 10) + " ");
            } else {
                text.append(Utils.doubleToString(inst.classValue(), 10, 3) + " ");
            }

            // predicted
            double pred = classifier.classifyInstance(inst);
            if (Instance.isMissingValue(pred)) {
                text.append(Utils.padLeft("?", 10) + " ");
            } else {
                text.append(Utils.doubleToString(pred, 10, 3) + " ");
            }

            // err
            if (!inst.classIsMissing() && !Instance.isMissingValue(pred)) {
                text.append(Utils.doubleToString(pred - inst.classValue(), 10, 3));
            }
        }

        // additional Attributes
       /* if (m_OutputAdditionalAttributesRange != null) {
            text.append(" (");
            boolean first = true;
            for (int i = 0; i < inst.numAttributes() - 1; i++) {
                if (m_OutputAdditionalAttributesRange.isInRange(i)) {
                    if (!first) {
                        text.append(",");
                    } else {
                        first = false;
                    }
                    text.append(inst.toString(i));
                }
            }
            text.append(")");
        }*/

        text.append("\n");
        return text.toString();
    }
}
